////
/// @group core.box-shadows
////

@use "sass:list";
@use "sass:map";
@use "sass:math";
@use "sass:meta";
@use "sass:string";
@use "theme/colors";
@use "theme/theme";
@use "transition/transition";
@use "utils";

/// The default box-shadow color to use for all material design box shadows.
/// @type Color
$box-shadow-color: colors.$black !default;

/// The opacity to apply for the first box shadow
/// @type Number
$box-shadow-1-opacity: 0.2 !default;

/// The box shadow values to apply based on elevation for the first box shadow.
/// @type Map
$box-shadow-1-layers: (
  0: "0px 0px 0px 0px",
  1: "0px 2px 1px -1px",
  2: "0px 3px 1px -2px",
  3: "0px 3px 3px -2px",
  4: "0px 2px 4px -1px",
  5: "0px 3px 5px -1px",
  6: "0px 3px 5px -1px",
  7: "0px 4px 5px -2px",
  8: "0px 5px 5px -3px",
  9: "0px 5px 6px -3px",
  10: "0px 6px 6px -3px",
  11: "0px 6px 7px -4px",
  12: "0px 7px 8px -4px",
  13: "0px 7px 8px -4px",
  14: "0px 7px 9px -4px",
  15: "0px 8px 9px -5px",
  16: "0px 8px 10px -5px",
  17: "0px 8px 11px -5px",
  18: "0px 9px 11px -5px",
  19: "0px 9px 12px -6px",
  20: "0px 10px 13px -6px",
  21: "0px 10px 13px -6px",
  22: "0px 10px 14px -6px",
  23: "0px 11px 14px -7px",
  24: "0px 11px 15px -7px",
) !default;

/// The opacity to apply for the second box shadow
/// @type Number
$box-shadow-2-opacity: 0.14 !default;

/// The box shadow values to apply based on elevation for the second box shadow.
/// @type Map
$box-shadow-2-layers: (
  0: "0px 0px 0px 0px",
  1: "0px 1px 1px 0px",
  2: "0px 2px 2px 0px",
  3: "0px 3px 4px 0px",
  4: "0px 4px 5px 0px",
  5: "0px 5px 8px 0px",
  6: "0px 6px 10px 0px",
  7: "0px 7px 10px 1px",
  8: "0px 8px 10px 1px",
  9: "0px 9px 12px 1px",
  10: "0px 10px 14px 1px",
  11: "0px 11px 15px 1px",
  12: "0px 12px 17px 2px",
  13: "0px 13px 19px 2px",
  14: "0px 14px 21px 2px",
  15: "0px 15px 22px 2px",
  16: "0px 16px 24px 2px",
  17: "0px 17px 26px 2px",
  18: "0px 18px 28px 2px",
  19: "0px 19px 29px 2px",
  20: "0px 20px 31px 3px",
  21: "0px 21px 33px 3px",
  22: "0px 22px 35px 3px",
  23: "0px 23px 36px 3px",
  24: "0px 24px 38px 3px",
) !default;

/// The opacity to apply for the third box shadow
/// @type Number
$box-shadow-3-opacity: 0.12 !default;

/// The box shadow values to apply based on elevation for the third box shadow.
/// @type Map
$box-shadow-3-layers: (
  0: "0px 0px 0px 0px",
  1: "0px 1px 3px 0px",
  2: "0px 1px 5px 0px",
  3: "0px 1px 8px 0px",
  4: "0px 1px 10px 0px",
  5: "0px 1px 14px 0px",
  6: "0px 1px 18px 0px",
  7: "0px 2px 16px 1px",
  8: "0px 3px 14px 2px",
  9: "0px 3px 16px 2px",
  10: "0px 4px 18px 3px",
  11: "0px 4px 20px 3px",
  12: "0px 5px 22px 4px",
  13: "0px 5px 24px 4px",
  14: "0px 5px 26px 4px",
  15: "0px 6px 28px 5px",
  16: "0px 6px 30px 5px",
  17: "0px 6px 32px 5px",
  18: "0px 7px 34px 6px",
  19: "0px 7px 36px 6px",
  20: "0px 8px 38px 7px",
  21: "0px 8px 40px 7px",
  22: "0px 8px 42px 7px",
  23: "0px 9px 44px 8px",
  24: "0px 9px 46px 8px",
) !default;

/// Creates a `box-shadow` string based on the material design guidelines and
/// elevation.
///
/// @param {Number} z-value - This should be a number from 0 to 24 representing
/// the box shadow elevation.
/// @param {Color} color [$box-shadow-color] - The color to use for the box
/// shadow
/// @param {Number} opacity-boost [0] - This will be applied to each shadow
/// value opacity if needed.
/// @returns {String} The generated box shadow
@function box-shadow($z-value, $color: $box-shadow-color, $opacity-boost: 0) {
  @if meta.type-of($z-value) != "number" or not math.is-unitless($z-value) {
    @error "$z-value must be a unitless number, but received '#{$z-value}'";
  }

  @if $z-value < 0 or $z-value > 24 {
    @error "$z-value must be between 0 and 24, but received '#{$z-value}'";
  }

  $shadow-1-value: map.get($box-shadow-1-layers, $z-value);
  $shadow-1-color: rgba($color, $box-shadow-1-opacity + $opacity-boost);

  $shadow-2-value: map.get($box-shadow-2-layers, $z-value);
  $shadow-2-color: rgba($color, $box-shadow-2-opacity + $opacity-boost);

  $shadow-3-value: map.get($box-shadow-3-layers, $z-value);
  $shadow-3-color: rgba($color, $box-shadow-3-opacity + $opacity-boost);

  @return #{"#{$shadow-1-value} #{$shadow-1-color}"},
    #{"#{$shadow-2-value} #{$shadow-2-color}"},
    #{"#{$shadow-3-value} #{$shadow-3-color}"};
}

/// Generates styles for a new elevated surface with box shadow.
///
/// @param {Number} z-value - This should be a number from 0 to 24 representing
/// the box shadow elevation.
/// @param {Color} color [$box-shadow-color] - The color to use for the box shadow
/// @param {Number} opacity-boost [0] - This will be applied to each shadow
/// value opacity if needed.
/// @param {Boolean} disable-colors [theme.$disable-dark-elevation] - Set to
/// `true` to prevent the background color and text color from being updated as
/// well.
@mixin box-shadow(
  $z-value,
  $color: $box-shadow-color,
  $opacity-boost: 0,
  $disable-colors: theme.$disable-dark-elevation
) {
  @include theme.create-surface($z-value, $disable-colors);

  box-shadow: box-shadow($z-value, $color, $opacity-boost);
}

/// Used to create a performant box-shadow transition using a pseudo element.
///
/// @param {String} start-shadow - The starting box shadow. This can be `null` if
/// there should be no box shadow by default.
/// @param {String} end-shadow - The ending box shadow. It is not possible to go
/// from shadow -> no shadow.
/// @param {List|String} active-selectors - The selectors that cause the shadow
/// to change to the `$end-shadow`.
/// @param {Number} duration [transition.$linear-duration] - The transition
/// duration
/// @param {Boolean} pseudo-before [true] - Set this to `false` to use the
/// `::after` pseudo element instead of `::before`.
/// @param {Number} pseudo-z-index [0] - Optional z-index to set to the pseudo
/// element.
/// @param {Boolean} pseudo-fixed [false] - Set to `true` if the pseudo element
/// should use `position: fixed` instead of `position: absolute`.
/// @param {Boolean} create-pseudo [true] - Set to `false` if the pseudo
/// element should not be created.
/// @param {Boolean} position-relative [true] - Set this to `false` if the
/// element should not gain `position: relative` styles through this mixin
@mixin box-shadow-transition(
  $start-shadow,
  $end-shadow,
  $active-selectors,
  $duration: transition.$linear-duration,
  $pseudo-before: true,
  $pseudo-z-index: 0,
  $pseudo-fixed: false,
  $create-pseudo: true,
  $position-relative: true
) {
  $pseudo-selector: if($pseudo-before, "&::before", "&::after");
  $suffix: string.slice($pseudo-selector, 2);
  $active-string: "";

  box-shadow: $start-shadow;

  @if $end-shadow {
    @if $position-relative {
      position: relative;
    }

    #{$pseudo-selector} {
      @if $create-pseudo {
        @include utils.pseudo-element($pseudo-z-index, $pseudo-fixed);
      }

      box-shadow: $end-shadow;
      opacity: 0;
      transition: opacity $duration transition.$linear-timing-function;
    }

    @if meta.type-of($active-selectors) == string {
      $active-string: $active-selectors + $suffix;
    } @else if meta.type-of($active-selectors) == list {
      @for $i from 1 to list.length($active-selectors) + 1 {
        $selector: list.nth($active-selectors, $i);

        $prefix: $active-string + if($i > 1, ", ", "");
        $active-string: $prefix + $selector + $suffix;
      }
    }

    @if $active-selectors == "&:hover" {
      @include utils.mouse-hover {
        #{$active-string} {
          opacity: 1;
        }
      }
    } @else {
      #{$active-string} {
        opacity: 1;
      }
    }
  }
}

/// Convenience helper to animate between two different material design box
/// shadow values.
///
/// Note: This will need to be included at the end of a block with the new Sass
/// rules or prefix styles with `& {}`
///
/// @param {Number} start-z-value - The starting elevation (a number from 0 to 24)
/// @param {Number} end-z-value - The ending elevation (a number from 0 to 24)
/// @param {List|String} active-selectors - The selectors that cause the shadow
/// to change to the `$end-shadow`.
/// @param {Boolean} pseudo-before [true] - Set this to `false` to use the
/// `::after` pseudo element instead of `::before`.
/// @param {Number} pseudo-z-index [0] - Optional z-index to set to the pseudo
/// element.
/// @param {Number} duration [transition.$linear-duration] - The transition
/// duration
/// @param {Color} color [$box-shadow-color] - The color to use for the box
/// shadow
/// @param {Number} opacity-boost [0] - This will be applied to each shadow
/// value opacity if needed.
/// @param {Boolean} position-relative [true] - Set this to `false` if the
/// element should not gain `position: relative` styles through this mixin
@mixin elevation-transition(
  $start-z-value,
  $end-z-value,
  $active-selectors,
  $pseudo-before: true,
  $pseudo-z-index: 0,
  $duration: transition.$linear-duration,
  $color: $box-shadow-color,
  $opacity-boost: 0,
  $position-relative: true
) {
  $start-shadow: if(
    $start-z-value == none or $start-z-value == 0,
    none,
    box-shadow($start-z-value, $color, $opacity-boost)
  );
  $end-shadow: if(
    $end-z-value == none or $end-z-value == 0,
    none,
    if($end-z-value, box-shadow($end-z-value, $color, $opacity-boost), null)
  );

  @include box-shadow-transition(
    $start-shadow,
    $end-shadow,
    $active-selectors,
    $duration,
    $pseudo-before,
    $pseudo-z-index,
    $position-relative: $position-relative
  );
}
